package p2t;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;

public final class Migrator {

	/**
	 * Main method to execute migration.
	 * 
	 * @param args
	 */
	public static void main(String[] args) throws Exception {
		doMigration();
	}
   
	/**
	 * This method executes the migration logic.
	 * 
	 * @throws IOException if an error occurred while reading/writing files.
	 */
	public static void doMigration() throws IOException {

		// Locate pidgin log directory
		File pidginLogDir = new File(getPidginLogDir());
		if (!pidginLogDir.exists()) {
			System.out.println("Cannot find pidgin logs at "
					+ pidginLogDir.getPath());
      return;
		}

		// Locate Thunderbird log directory
		File tbirdLogDir = new File(getTBirdLogDir());
		if (!tbirdLogDir.exists()) {
			System.out.println("Cannot find thunderbird logs at "
					+ tbirdLogDir.getPath());
      return;
		}
		
		FilenameFilter onlyVisibleFiles = new IgnoreHiddenFileFilter();
		
		// first find out all account types in pidgin
		File[] accountTypes = pidginLogDir.listFiles(onlyVisibleFiles);
		
		if (accountTypes != null && accountTypes.length > 0) {
      // iterate over the account types
			for (File accountType: accountTypes) {
				// check if account type exists in tbird; if not, create it
				File tbAccountType = new File(tbirdLogDir, accountType.getName());
				if (!tbAccountType.exists()) {
					tbAccountType.mkdir();
				}
				
        // get list of accounts in the account type
				File[] accounts = accountType.listFiles(onlyVisibleFiles);
				if (accounts != null && accounts.length > 0) {
				  // iterate over the accounts in the account type
					for (File account: accounts) {
						// check if account exists in tbird; if not create it
						File tbAccount = new File(tbAccountType, account.getName());
						if (!tbAccount.exists()) {
							tbAccount.mkdir();
						}
						
						System.out.println("Migrating account " + account.getName() + " of type " + accountType.getName() + " ...");
						
						// get list of buddies in the account
						File[] buddies = account.listFiles(onlyVisibleFiles);
						if (buddies != null && buddies.length > 0) {
						  // iterate over the buddy list in the account
							for (File buddy: buddies) {
								// check if buddy dir exists in tbird; if not create it
								File tbBuddy = new File(tbAccount, buddy.getName());
								if (!tbBuddy.exists()) {
									tbBuddy.mkdir();
								}
								
								System.out.println("\tMigrating buddy logs for " + buddy.getName() + " ...");
								
								// get list of log files for the buddy
								File[] buddyLogs = buddy.listFiles(onlyVisibleFiles);
								if (buddyLogs != null && buddyLogs.length > 0) {
								  // iterate over the buddy logs
									for (File buddyLog: buddyLogs) {
										// check if buddy log exists in tbird; if not create it
										File tbBuddyLog = new File(tbBuddy, pdLogFileName2TBLogFileName(buddyLog.getName()));
										boolean alreadyExisting = tbBuddyLog.exists();
										
										BufferedWriter fout = null;
										BufferedReader fin = null;
										try {
											// if already existing, append to existing logs
											// else migrate whole 
											if (alreadyExisting) {
												// TODO 
											} else {
												fin = new BufferedReader(new FileReader(buddyLog));
												fout = new BufferedWriter(new FileWriter(tbBuddyLog));
												String s = fin.readLine();
												PDLogHeader parsedHeader = pdLogHeader2TBLogHeader(s);
                        // write out header
												fout.write(parsedHeader.translatedTBHeader);
												StringBuilder logLine = new StringBuilder();
												while ((s = fin.readLine()) != null) {
													if (s.matches("\\(.*\\d{1,2}\\:\\d{1,2}:\\d{1,2}.*\\).*") && logLine.length() > 0) {
														fout.write(pdLog2TBLog(logLine.toString(), parsedHeader));
														logLine.setLength(0);
													}
													logLine.append(s);
												}
												if (logLine.length() > 0) {
													fout.write(pdLog2TBLog(logLine.toString(), parsedHeader));
													logLine.setLength(0);
												}												
											}											
										} finally {
											if (fout != null) {
												fout.close();
											}
											if (fin != null) {
												fin.close();
											}
										}
									}
								}
							}
						}
					}
				}
			}
		} else {
			System.out.println("Nothing to migrate!!!");
		}

	}
  
  /**
   * This method translates a line in a pidgin log to a line in thunderbird log.
   *
   * @param pdLog the pidgin log line.
   * @param headerInfo log file header info (for use in the translation).
   * @return translated thunderbird log line. 
   */
	private static String pdLog2TBLog(String pdLog, PDLogHeader headerInfo) {
		try {
			StringBuilder tbLogHeader = new StringBuilder();
			
			tbLogHeader.append("{");
			
			tbLogHeader.append("\"date\":\"");
			Calendar cal = Calendar.getInstance();
			cal.clear();
			String[] sa = pdLog.split("\\)", 2);
			String[] dateTokens = sa[0].split("\\s");
			switch (dateTokens.length) {
			case 1:
				for (int i = 0 ; i < pidgin24HDateFormats.length ; i++) {
					try {
						cal.setTime(pidgin24HDateFormats[i].parse(headerInfo.logDate+ " " + sa[0].substring(1)));
						break;
					} catch (ParseException e) {
						if (i == pidgin24HDateFormats.length-1) {
							throw e;
						}
					}
				}
				break;
			case 2:
				for (int i = 0 ; i < pidginAMPMDateFormats.length ; i++) {
					try {
						cal.setTime(pidginAMPMDateFormats[i].parse(headerInfo.logDate+ " " + sa[0].substring(1)));
						break;
					} catch (ParseException e) {
						if (i == pidginAMPMDateFormats.length-1) {
							throw e;
						}
					}
				}
				break;
			case 3:
				for (int i = 0 ; i < pidginAMPMDateFormats.length ; i++) {
					try {
						cal.setTime(pidginAMPMDateFormats[i].parse(sa[0].substring(1)));
						break;
					} catch (ParseException e) {
						if (i == pidginAMPMDateFormats.length-1) {
							throw e;
						}
					}
				}
				break;
			}
			tbLogHeader.append(tbirdDateFormat.format(new Date(cal.getTimeInMillis()-cal.getTimeZone().getOffset(cal.getTimeInMillis()))));
			tbLogHeader.append("Z\"");
			
			String[] sa2 = sa[1].split("\\:", 2);
			tbLogHeader.append(",\"who\":\"");
			tbLogHeader.append(sa2[0].substring(1));
			tbLogHeader.append("\"");
			
			tbLogHeader.append(",\"text\":\"");
			tbLogHeader.append("<body xmlns=\\\"").append(headerInfo.protocol).append(":client\\\">").append((sa2.length > 1 ? sa2[1].substring(1) : "")).append("</body>");
			tbLogHeader.append("\"");
			
			tbLogHeader.append(",\"flags\":");
			if (headerInfo.whoAmI.startsWith(sa2[0].substring(1))) {
				tbLogHeader.append("[\"outgoing\"]");
			} else {
				tbLogHeader.append("[\"incoming\"]");
			}
			
			tbLogHeader.append(",\"alias\":\"");
			tbLogHeader.append(sa2[0].substring(1));
			tbLogHeader.append("\"");
			
			tbLogHeader.append("}");
			return tbLogHeader.toString();
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

  /**
   * This method translates a pidgin log file header to a thunderbird log file header.
   *
   * @param pdLogHeader the pidgin log file header.
   * @return translated thunderbird log file header. 
   */
	private static PDLogHeader pdLogHeader2TBLogHeader(String pdLogHeader) {
		// parse pidgin log header
		String[] tokens = pdLogHeader.split("\\s");
		if (tokens.length < 9) {
			throw new RuntimeException();
		}
		
		
		try {
			PDLogHeader retObj = new PDLogHeader();
			StringBuilder tbLogHeader = new StringBuilder();
			
			tbLogHeader.append("{");
			
			tbLogHeader.append("\"date\":\"");
			Calendar cal = Calendar.getInstance();
			cal.clear();
			if (tokens.length == 10) {
				for (int i = 0 ; i < pidginAMPMDateFormats.length ; i++) {
					try {
						cal.setTime(pidginAMPMDateFormats[i].parse(tokens[4]+ " " + tokens[5] + " " + tokens[6]));
						break;
					} catch (ParseException e) {
						if (i == pidginAMPMDateFormats.length-1) {
							throw e;
						}
					}
				}
			} else {
				for (int i = 0 ; i < pidgin24HDateFormats.length ; i++) {
					try {
						cal.setTime(pidgin24HDateFormats[i].parse(tokens[4]+ " " + tokens[5]));
						break;
					} catch (ParseException e) {
						if (i == pidgin24HDateFormats.length-1) {
							throw e;
						}
					}
				}
			}
			cal.setTimeZone(TimeZone.getDefault());
			tbLogHeader.append(tbirdDateFormat.format(new Date(cal.getTimeInMillis()-cal.getTimeZone().getOffset(cal.getTimeInMillis()))));
			tbLogHeader.append("Z\"");
			retObj.logDate = tokens[4];
			
			tbLogHeader.append(",\"name\":\"");
			tbLogHeader.append(tokens[2]);
			tbLogHeader.append("\"");
			
			tbLogHeader.append(",\"title\":\"");
			tbLogHeader.append(tokens[2]);
			tbLogHeader.append("\"");
			
			tbLogHeader.append(",\"account\":\"");
			tbLogHeader.append(tokens[tokens.length-2]);
			retObj.whoAmI = tokens[tokens.length-2];
			tbLogHeader.append("\"");
			
			tbLogHeader.append(",\"protocol\":\"");
			tbLogHeader.append(tokens[tokens.length-1].substring(1, tokens[tokens.length-1].length()-1));
			retObj.protocol = tokens[tokens.length-1].substring(1, tokens[tokens.length-1].length()-1);
			tbLogHeader.append("\"");
			
			tbLogHeader.append("}");
			retObj.translatedTBHeader = tbLogHeader.toString();
			
			return retObj;
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
	
  /**
   * This method determines the thunderbird log file name correspoding to a pidgin log file.
   *
   * @param pdLogFileName the pidgin log file name.
   * @return thunderbird log file name. 
   */
	private static String pdLogFileName2TBLogFileName(String pdLogFileName) {
		String s = pdLogFileName.split("[^0-9\\.-]")[0];
		if (s.endsWith(".")) {
			s = s.substring(0, s.length()-1);
		} 
		
		return s+".json";
	}
	
  /**
   * This method finds out the thunderbird log file directory.
   *
   * @return filesystem path to thunderbird log file directory.
   */
	private static String getTBirdLogDir() {
		StringBuilder sbuf = new StringBuilder(System.getProperty("user.home"));

		String osName = System.getProperty("os.name");
		if (osName != null && osName.startsWith("Windows")) {
			sbuf.append(File.separatorChar).append("Application Data");
		}
		sbuf.append(File.separatorChar).append("Thunderbird");
		sbuf.append(File.separatorChar).append("Profiles");

		File f = new File(sbuf.toString());
		if (f.exists()) {
			String[] defaultProfileName = f.list(new FilenameFilter() {

				@Override
				public boolean accept(File dir, String name) {
					if (name.endsWith("default")) {
						return true;
					} else {
						return false;
					}
				}
			});

			if (defaultProfileName != null && defaultProfileName.length > 0) {
				sbuf.append(File.separatorChar).append(defaultProfileName[0]);
			} else {
				sbuf.append(File.separatorChar).append("default");
			}
		}

		sbuf.append(File.separatorChar).append("logs");
		return sbuf.toString();
	}

  /**
   * This method finds out the pidgin log file directory.
   *
   * @return filesystem path to pidgin log file directory.
   */
	private static String getPidginLogDir() {
		StringBuilder sbuf = new StringBuilder(System.getProperty("user.home"));

		String osName = System.getProperty("os.name");
		if (osName != null && osName.startsWith("Windows")) {
			sbuf.append(File.separatorChar).append("Application Data");
		}
		sbuf.append(File.separatorChar).append(".purple");
		sbuf.append(File.separatorChar).append("logs");

		return sbuf.toString();
	}

  /**
   * Create a new instance of this class.
   */
  private Migrator() {
    // private on purpose to prevent instantiation
  }

	/**
   * Private inner class for filtering out hidden files from files listing.
   */
	private static class IgnoreHiddenFileFilter implements FilenameFilter {

		@Override
		public boolean accept(File dir, String name) {
			if (name.startsWith(".")) {
				return false;
			} else {
				return true;
			}
		}
		
	}
	
	private static DateFormat[] pidginAMPMDateFormats = new SimpleDateFormat[] {
		new SimpleDateFormat("MM/dd/yyyy hh:mm:ss a"),
		new SimpleDateFormat("yyyy-MM-dd hh:mm:ss a")
		};
	private static DateFormat[] pidgin24HDateFormats = new SimpleDateFormat[] {
		new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"),
		new SimpleDateFormat("MM/dd/yyyy HH:mm:ss")
		};
	private static DateFormat tbirdDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
	
  /**
   * Private inner class for hold header data.
   */
	private static class PDLogHeader {
		String whoAmI;
		String protocol;
		String logDate;
		String translatedTBHeader;
	}
}
